package de.tum.icuproject.network;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import de.tum.icuproject.utils.AppUtils;
import dhm.exch.am.data.AMPacketBase;
import dhm.exch.am.data.ChannelInterface;

/**
 * Client for the MDC application
 * @author stefanoturchetta
 *
 */
public class MDCClient implements Runnable {

	private Socket clientSocket;
	private String host;
	private int port;

	private Handler handler;
	
	private int playerId;
	private int sessionId;
	
	private List<Integer> initialParameters;

	private static final int MAX_TIMEOUT = 5000; // timeout for connecting and
	private static final int BUFFSIZE = 250000; // how many bytes our incoming

	public MDCClient(String host, int port, Handler handler) {
		super();
		this.host = host;
		this.port = port;
		this.handler = handler;
	}

	private boolean connect() {
		this.clientSocket = new Socket();
		try {
			SocketAddress socketAddress = new InetSocketAddress(this.host, this.port);
			this.clientSocket.connect(socketAddress, MAX_TIMEOUT);
			return true;
		} catch (UnknownHostException e) {
			Log.e("MDC_CLIENT_CONNECT", e.getMessage());
		} catch (IOException e) {
			Log.e("MDC_CLIENT_CONNECT", e.getMessage());
		}
		return false;
	}

	private void disconnect() {
		if (this.clientSocket != null) {
			try {
				this.clientSocket.close();
			} catch (IOException e) {
				Log.e("MDC_CLIENT_DISCONNECT", e.getMessage());
			}
			this.clientSocket = null;
		}
	}

	private boolean canRun() {
		return this.clientSocket != null && this.clientSocket.isConnected();
	}

	private void start() {
		new Thread(this).start();
	}

	@Override
	public void run() {
		boolean run = true;
		if (!this.canRun()) {
			run = this.connect();
		}
		if (run) {
			try {
				// start value is 1
				this.sendStartOrStop(playerId, sessionId, 1);
				// send initial parameters
				this.sendParameters(initialParameters);
				// start reading responses
				BufferedInputStream bis = new BufferedInputStream(this.clientSocket
						.getInputStream(), BUFFSIZE);
				int bytesRead;
				byte buf[] = new byte[BUFFSIZE];
				while ((bytesRead = bis.read(buf)) > 0 && this.canRun()) {
					int offset = 0;
					if (buf[0] == 24) {
						while (offset < bytesRead) {
							ByteBuffer byteBuffer = ByteBuffer.wrap(buf, offset, bytesRead);
							byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
							AMPacketBase p = new AMPacketBase(byteBuffer);
							this.addPacket(p);
							offset += p.$size;
							if (p.$moduleID == 2)  //seems to receive more bytes, creating error packet
								break; 
						}
					}
				}
			} catch (IOException e) {
				Log.e("IOException", "Error during run()");
			}
		}
	}

	private void addPacket(AMPacketBase p) {
		Log.d("NEW_PACKET", p.toString());
		// Module identifier of packet
		if (p.$moduleID == 0) {
			Message msg = Message.obtain(); 
			Bundle bundle = new Bundle();
			bundle.clear(); 
			bundle.putDouble(AppUtils.HEART_RATE,
					p.getDoubleSampleChannel(0 /*channel*/, 
							0 /*first sample*/));
			bundle.putDouble(AppUtils.RESPIRATION,
					p.getDoubleSampleChannel(1, 0));
			bundle.putDouble(AppUtils.SPEED,
					p.getDoubleSampleChannel(2, 0));
			bundle.putDouble(AppUtils.LOAD,
					p.getDoubleSampleChannel(3, 0));
			msg.setData(bundle); 
			this.handler.sendMessage(msg); 
		}
	}

	/**
	 * Send game parameters to the server
	 * @param parameters
	 */
	public void sendParameters(List<Integer> parameters) {
		if (this.canRun()) {
			int numChannels = parameters.size();
			AMPacketBase packet = new AMPacketBase(2, numChannels, ChannelInterface.F32_TYPE, 1, 1);
			int chan = 0;
			for (Integer parameter : parameters) {
				packet.addSample(chan++, parameter);
			}	
			Log.d("PARAMS_PACKET", packet.toString());
			try {
				packet.send(this.clientSocket);
			} catch (IOException e) {
				Log.e("PARAMS_PACKET", e.toString());
			}
		}
	}

	// isStart is 1 if start, 0 otherwise
	private void sendStartOrStop(int playerId, int sessionId, int isStart) {
		String tag = "STOP_PACKET";
		if (isStart == 1) {
			tag = "START_PACKET";
		}
		AMPacketBase packet = new AMPacketBase(3, 3, ChannelInterface.F32_TYPE, 1, 1);
		packet.addSample(0, isStart);
		packet.addSample(1, playerId);
		packet.addSample(2, sessionId);
		Log.d(tag, packet.toString());
		try {
			packet.send(this.clientSocket);
		} catch (IOException e) {
			Log.e(tag, e.toString());
		}
	}

	/**
	 * Send start command to the server
	 * @param playerId
	 * @param sessionId
	 */
	public void sendStart(int playerId, int sessionId, List<Integer> parameters) {
		this.playerId = playerId;
		this.sessionId = sessionId;
		this.initialParameters = parameters;
		this.start();
	}

	/**
	 * Send stop command to the server
	 * @param playerId
	 * @param sessionId
	 */
	public void sendStop() {
		// stop value is 0
		this.sendStartOrStop(this.playerId, this.sessionId, 0);
		// TODO: here I should wait for the server response before disconnecting
		this.disconnect();
	}

}
